/*
* 程序名: clientdemo1.cpp，此程序用于演示socket的客户端
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>

#include <fstream>

using namespace std;

class ctcpclient
{
public:
	int m_clientfd;
	string m_ip;
	unsigned short m_port;

	ctcpclient():m_clientfd(-1){}

	//向服务器发起连接请求，成功返回true，失败返回false
	bool connect(const string &in_ip, const unsigned short in_port)
	{
		if (m_clientfd != -1) return false;  // 如果socket已连接，直接返回失败。
		
		m_ip = in_ip; m_port = in_port;
		
		// 第一步：创建客户端的socket
		if( (m_clientfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) return false;
		

		// 第二步：向服务器发起连接请求
		struct sockaddr_in servaddr;
		memset(&servaddr,0,sizeof(servaddr));
		servaddr.sin_family = AF_INET;
		servaddr.sin_port = htons(m_port);	// 指定服务端的通信端口。


		struct hostent *h;	//用于存放服务端ip的结构体
		if ( (h = gethostbyname(m_ip.c_str())) == 0) //把字符串格式的ip转换成结构体
		{
			::close(m_clientfd); m_clientfd=-1; return false;
		}
		memcpy(&servaddr.sin_addr, h->h_addr_list[0], h->h_length); //指定服务端的ip地址。
		
	
		if(::connect(m_clientfd, (struct sockaddr *)&servaddr, sizeof(servaddr))==-1)
		{
			::close(m_clientfd); m_clientfd=-1; return false;
		}
		return true;
	}

	// 向服务器发送报文，成功返回true，失败返回false。
	bool send(const string &buffer) // buffer不要用const char *，因为string可以用c_str()转为c风格的字符串。
	{
		if (m_clientfd == -1) return false;
		
		if( (::send(m_clientfd,buffer.data(),buffer.size(),0)) <= 0 ) return false;
		
		return true;
	}

	// 向服务器发送报文（二进制数据），成功返回true，失败返回false。
	bool send(void * buffer, const size_t size) 
	{
		if (m_clientfd == -1) return false;
		
		if( (::send(m_clientfd,buffer,size,0)) <= 0 ) return false;
		
		return true;
	}


	// 接收服务端的报文，成功返回true，失败返回false。
	// buffer-存放接收到的报文内容，maxlen-本次接收报文的最大长度。
	bool recv(string &buffer, const size_t maxlen)
	{
		// 直接操作string对象的内存，必须保证：1）不能越界； 2）操作后手动设置数据的大小。
		buffer.clear();		// 清空容器
		buffer.resize(maxlen);  // 设置容器的大小为maxlen。
		int readn = ::recv(m_clientfd, &buffer[0], buffer.size(), 0);	// 直接操作buffer的内存。
		if (readn <= 0){ buffer.clear(); return false;};
		buffer.resize(readn);   // 重置buffer的实际大小。
		return true;
	}

	// 断开与服务端的连接。
	bool close()
	{
		if (m_clientfd == -1) return false;
		
		::close(m_clientfd);
		m_clientfd = -1;
		return true;
	}

	// 向服务端发送文件内容。
	bool sendfile(const string &filename, const size_t filesize)
	{
		// 以二进制的方式打开文件。
		ifstream fin(filename, ios::binary);
		if (fin.is_open() == false) 
		{
			cout << "打开文件" << filename << "失败。\n"; return false;
		}

		int onread = 0;			// 每次调用fin.read()时打算读取的字节数。 每次应搬砖头数。
		int totalbytes = 0;		// 从文件中已读取的字节总数。已搬砖头数。
		char buffer[7];			// 存放读取数据的buffer。 每次搬7块砖头。

		while(true)
		{
			memset(buffer, 0, sizeof(buffer));

			if (filesize - totalbytes > 7) 
				onread = 7;
			else
				onread = filesize - totalbytes;

			fin.read(buffer, onread);

			if (send(buffer, onread) == false) 
				return false;
			
			totalbytes = totalbytes + onread;

			if (totalbytes == filesize) break;
		}


		return true;
	}
	~ctcpclient(){ close(); }
};



int main(int argc, char *argv[])
{
	if(argc!=5)
	{
		cout << "Using:./filetrans 服务端的IP 服务端的端口 文件名 文件大小\n";
		cout << "Example:./clientdemo1 192.168.101.139 5005 aaa.txt 2424\n\n";
		return -1;
	}
	
	ctcpclient tcpclient;
	if( tcpclient.connect(argv[1], atoi(argv[2])) == false)
	{
		perror("connect()"); return -1;
	}

	// 以下是发送文件的流程
	// 1）把待传输文件名和文件的大小告诉服务端。
	// 定义文件信息的结构体
	struct st_fileinfo{
		char filename[256];	// 文件名
		int  filesize;		// 文件大小
	}fileinfo;
	memset(&fileinfo, 0, sizeof(fileinfo));
	strcpy(fileinfo.filename, argv[3]);	// 文件名
	fileinfo.filesize = atoi(argv[4]);	// 文件大小
	// 把文件信息的结构体发给服务端。
	if (tcpclient.send(&fileinfo, sizeof(fileinfo)) == false)
	{
		perror("send"); return -1;
	}
	cout << "发送文件信息的结构体" << fileinfo.filename << "(" << fileinfo.filesize << ")。" << endl;

	// 2）等待服务端的确认报文（文件名和文件的大小确认）。
	string buffer;
	if (tcpclient.recv(buffer, 2) == false) 
	{
		perror("recv()"); return -1;
	}
	if (buffer != "ok")
	{
		cout << "服务端没有回复ok。\n"; return -1;
	}

	// 3) 发送文件内容。
	if (tcpclient.sendfile(fileinfo.filename, fileinfo.filesize) == false)
	{
		perror("sendfile()"); return -1;
	}

	// 4）等待服务端的确认报文（服务端已经接收完毕）。
	if (tcpclient.recv(buffer,2) == false)
	{
		perror("recv()"); return -1;
	}
	if (buffer != "ok") 
	{
		cout << "发送文件内容失败。\n"; return -1;
	}

	cout << "发送文件内容成功。\n";

	return 0;
}
